深入理解Delete(JavaScript)
深入理解Delete(JavaScript)
Delete 众所周知是删除对象中的属性. 但如果不深入了解delete的真正使用在项目中会出现非常严重的问题 (:
Following 是翻译 kangax 的一篇文章 "Understanding Delete";
PS:文章主要分为8部分, 有时间弄点瓜子儿, 整壶茶了解一下. (小编建议直接看原文地址, 以下翻译仅供自己学习使用);
相信大家如果有时间看完会有收获...也希望有大牛能指出其中翻译的不足...
目录:
§ Activation object / Variable object
§ Build-ins and DontDelete(嵌入式/不可删除)
§ Undeclared assignments (未声明任务)
§ FireBug confusion (奇异的FireBug)
§ Deleting variables via eval(通过eval删除变量)
§ Browsers compliance (浏览器兼容性)
================Enein翻译===================
先上例子:
忽略几个丢失分号. 这段代码你能看出什么问题?
当然这个问题很明显 "delete sum" 是不会成功的. delete 返回的值不应该是 "true" , "typeof sum" 返回的结果也不是"undefined"。造成问题的原因是 在JavaScript中"delete 是不可以删除变量的".
这个例子有问题? 排版问题? 是个变相题? 应该都不是. 上面的代码会在FireBug Console下正确输出.(你可以快速测试一下) 仿佛在FireBug下有它自己的删除规则. 这...给我干蒙了. 到底是怎么回事? 我们来讨论一下.
要想知道答案我们首先要先知道 "delete" 操作符在JavaScript中的实际是怎样工作的: (主要从3个方向 什么情况能正确删除, 什么时候不能删除, 为什么);
让我们带着疑问继续往下看:(I’ll try to explain this in details)
我们来看一个FireBug的古怪行为.并了解其实这是正常的.
我们将深入了解下 "声明变量","函数","加入属性"是怎么工作的并在适当的时候删除它们.我们还会看一下浏览器的兼容性以及其一些常见的Bugs, ECMAScript 5 strict mode 和如何改变delete 操作符的行为
PS:这一段是作者对Mozilla MSN 和 MSDN 上的两篇文章发表的个人看法(他会认为practically useless)这里不做翻译有兴趣的同学可以点其链接自行查看分析.
为什么它能删除对象的属性:
变量却不能, like this:
函数也不允许, like this:
注意当 属性不能被删除的时候将返回 false
要明白理解这些, 要需要进一步理解变量实例概念、属性的特性。(有限的是在JavaScript相关书籍中涉及的知识还是比较少的)以下就要详细的介绍.
(如果你不关心这些东西为什么工作方式是这样的,那就skip this chapter)
§ Type of Code (代码级别) [ps:代码级别是出于自己的理解]
在ECMAScript中有3种作用域: Global code(全局作用域), Function code(函数作用域), Eval code(Eval作用域) 以下对三种级别的描述.
Global code : 当一段文本做为一个程序的时候, 它是在全局作用域下执行的. 在浏览器环境中通常写在SCRIPT标签下的内容会被解析, 因为也算是全局作用域
Function code : 任何东西在function里是会随着function执行并执行.很明显这是属于函数作用域, 在浏览器中事件属性通过也会被当作函数作用域.(e.g <p onclick=""/>)
Eval code : 最后, 在eval函数体里的代码就是 Eval作用域.很快我们就会看到为什么这个类型是特殊的.
当ECMAScript的代码执行的时候, 它就一直在某一个执行上下文中, “Execution context 是一个抽象的实体” 它会使我们明白作用或和变量实例化的过程. 对以上中种类型的范围, 它们就是一个执行上下文.
当function被执行的时候, 这个实体的上下文就是"Function code", 当代码是在Global code下被执行的时候, 那么它就是 "Global code" , 也 so on.
就你像我们看到的那样, 执行上下文逻辑上属于一个 stack (栈) 首先它可能是执行在全局作用域下的, 它拥有自己的上下文, 在这段代码里, 可能还会调用一个function, 这个function也会有自己的上下文, 在这个function里有可能还会调用一个function, function还可以递归调用, 以此类推.
§ Activation object / Variable object
每个执行上下文都会和一个("Variable Object")可变的对象相关联, 和执行上下文类型类似, Variable Object 也是一个抽象的实体. 通过一种机制来描述变量初始化过程, 现在, 我们感兴趣的是 变量和函数声明的时候 实际上是作为 Variable Object 的属性被加入的.
当这个实体的执行上下文为 全局作用域的时候, 那么这个全局的对象会当做一个 "Variable Object" 这也就说明了, 为什么变量和函数的声明为全局的时候会 变成全局对象的属性了.
OK, 所以 全局变量 会变成 全局对象 的属性, 但对于局部变量它发生了什么, 在函数体内他们是怎么声明的, 很简单、它们变成了(Variable Object)变量对象的属性. 只是在作为Function code的时候有所不同, 一个变量对象不是一个全局对象. 但它会调用一个"Activation object"(激活对象), 每一次给函数分配上下文的时候Activation object将会被创建.
不仅仅是变量和函数的声明会成为Activation object的属性, 函数的形参(形式参数:对应实际参数)和特殊对象Arguments object 注意 Activation object是一种内部机制, 是永远不可能访问的程序代码.
最后, 在Eval code中的变量声明是作为 创建变量对象上下文调用时的属性 Eval code 简单的使用变量对象的执行上下文, 代码执行是这样的:
我们就快要明白了, 现在我们清楚的明白变量到底发生了什么(它们属性之间的变换), 剩下 Property attributes了.每个属性都会存在0个或多个属性包括(ReadOnly(只读),DontEnum(不可枚举), DontDelete(不可删除)) 对于今天的话题我们只讨论DontDelete.
当声明变量和函数变成变量对象的属性或者一个激活对象(作为一个Function code), 或者全局对象(Global code), 这些属性被创建并含有DontDelete特性.无论怎么样, 一些显式(隐式)属性分配上也会创建不含有Dontdelete的属性 为什么有的能有的不能:
§ Build-ins and DontDelete(嵌入式和不可删除)
这节我们说的是, 属性的一些特殊特性来控制这些属性可否被删除(注意: 一些内置的属性会被默认指定成DontDelete, 固不能被删除)特殊arguments变量(现在我们知道它是激活对象的属性)有DontDelete. 同样的一些function实例的length属性也存在DontDelete:
同样, 函数的形参也是有DontDelete的也是不可删除的.
§ Undeclared assignments (未声明的任务)
未声明的任务创建一个全局对象的属性. 除非在全局对象之前你能找到这个属性是属性哪个作用域链的. 现在我们清楚,属性任务和变量声明之间的不同, 后者是是DontDelete属性, 前者则不是(它应该清楚为什么未被声明的会创建不含有DontDelete的属性).
注意在属性创建期间, 其属性是被确定的. 后面的任务是不可改变已存在的属性的, 明白这点是很重要的.
§ FireBug confusion (奇异的FireBug)
在FireBug发生了什么? 之前说过在FireBug console中变量的声明是可以删除的. 这违背了我们之前说的所有? 好吧, 之前我说过, Eval code的变量声明时有着特殊的行为, 变量声明在Eval code里实际上是创建了没有DontDelete的属性:
同样对于在Function code里调用:
这就是重点, 所有在Firebug console中执行的代码会被当成是 Eval code来进行解析 所以
和console的不同.
§ Deleting variables via eval(通过eval删除变量)
最有意思的是eval的特性, 另一方面ECMAScript能从技术上允许我们去删除不可删除的属性.在同一个上下文中function的声明是可以被同名变量重写的.
注意 function声明优先,重写同名变量(或者, 换句话说, 在变量对象中存在了相同属性). 这是因为 函数声明被实例是在变量声名(Variable declarations)之后, 是允许被覆盖的不仅函数声明替换这前属性的值, 它也能替换它的属性.
如果我们通过eval来声明function那么还是可以替换相应的属性, 因为在eval里创建的变量声明没有DontDelete, 以下示例会从本质上删除存在的DontDelete特性。
不幸的事, 我尝试各种不能工作的场景, 有可能我会有所疏漏.
§ Browsers compliance (浏览器兼容性)
学习这些东西的工作原理是很实用的, 实践至上. 在浏览器兼容上会存在多在的差异.作者做了很多的浏览器测试, 最主要的是属性中含有DontDelete是不可删除的,相反则然.
当今浏览器的脾气都是很友好的. 我测试的Opera 7.54+, Firefox 1.0+, Safari 3.1.2+, Chrome 4+浏览器都是可行的.
Safari 2.x and 3.0.4 是有问题的对于function的参数问题上;这些参数被看做没有DontDelete特性, 问题主要是我们可以detele它们. 实际上Safari 2.x存在更多的问题(删除没有引用的变量e.g delete 1)会抛异常, function的声明会创建可删除属性(不包括变量声名), 变量声明在eval中变为不可删除(除了function声明).
Konqueror (3.5)也同样(删除function参数会报错)
Gecko DontDelete Bug
Gecko 1.8.x browsers — Firefox 2.x, Camino 1.x, Seamonkey 1.x, etc. 显示出一个很有意思的bug “显式地设定一个属性是可以移除DontDelete特性, 即使这个属性通过变量或函数声明被创建”:
比较出乎意外的是IE 5.5 - 8 基本测试都通过了. 只是删除没有引用的会报错(e.g delete 1) , 但其实实际上IE中有更严重的BUG是关于全局对象的.
IE BUGS(IE bug)
这一章主要是说一下Internat Explorer下的BUGS:
在IE5.5-8, 下面的代码会报错(在全局域中执行)
这个也一样, 不同的异常, 看起来异常有趣.
这好像是在IE中 “在全局域中声明变量是不属于全局对象的属性的” 通过 this.x = 1 声明属性, 使用 delete x 会报错通过变量声明 var x = 1; 通过delete this.x删除也会报错.
但说的也不全部, 显示的创建一个属性在删除的时候是一直会报错的
现在, 相反 未声明的任务(将会在全局对象上)会创建可删除属性在IE里:
但你要尝试使用全局对象的属性的方式来删除, 则会报错:
小结: 只要是 delete this.x 都不会成功.
Misconceptions (歧义)
/*...*/
§ 'delete' and host objects
简单的推算一下delete如下:
- 如果这个运算对象没有引用, 返回 true.
- 如果不是Object的内部属性, 返回 true.
- 如果Object有属性但有DontDelete特性, 返回 false.
- 其它移除属性返回 true.
无论怎么, delete操作符在宿主对象上的行为也是不可预知的 这是其实是没有问题的, 宿主对象是允许(通过规范)去实现各种操作行为比如 read(内部实现[[Get]]方法), write(内部实现[[Put]]方法), delete(内部实现[[Delete]]方法).
之前我们已经讨论了IE的差异, delete 某一对象会抛异常, 在一些火狐版本中删除 window.location 抛出的异常, 你是不能相信delete 宿主对象的属性的返回值的. 看下面代码在FireFox:
删除window.alert返回的是true , 它的解析过程 :
One step : 被解析成一个引用(不会返回true);
two step : 是window的内部属性(不会返回true);
只有在真正 "delete window.alert" 的时候才真正删除了嘛? no 它还是没有被删除.
小结: 从来不要相信宿主对象
ECMAScript 规范 严格格式下会有很多限制, 在这几种情况下会报语法错误: delete 直接去删除 变量, 函数的参数, 函数定义, 另外, 当属性有内部属性[[Configurable]] == false 是会报 类型错误:
另外, 在删除未声明变量(或未指明的引用)抛出词法错误:
同样的在未指定确定类型的变量在严格模式(strict mode)下也是会报词法错误的:
现在我明白了, 在严格模式下的这些限制都是很有用的, ECMAScript strict mode 解决了很多问题, 而不是忽视它们, 从中我们也可以通过ECMAScript这些限制, 反向理解来学习更深入的知识.
这篇文章说的太长了, 如果你能静下心来好好看完, 那你将会明白很多, 这里我只是说了一部分关于Array的delete我这里就不说了, 但希望有兴趣的同学可以自己去尝试(你可以参考MDC for that particular explanation 文章).
这是里简单的做一下在JavaScript中delete的操作:
-
变量和函数声明属性要么是激活对象, 要么是全局对象
- 属性里有DontDelete特性的表示不可删除属性.
-
变量和函数声明只要是在"全局代码块"/"函数级代码块"中都会有 —— DontDelete.
- Functions的参数也是属于激活对象的属性, 所以也有 —— DontDelete.
-
变量和函数声明在Eval代码块中的, 都不会创建 —— DontDelete.
- 为对象加入新的属性(没有任何特性), 也是不会创建 —— DontDelete.
-
不管他们想怎样, 宿主对象对删除是会返回状态的.
================Enein翻译===================